//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 * usb_serial_download.c: serial over usb download command
 *
 * Copyright (c) 2004, Intel Corporation (alek.du@intel.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ident "$Id: usb_serial_download.c,v 1.1 2006/07/13 08:10:59 zhangyi Exp $"

#if 0
#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#include <blob/download.h>
#include <blob/command.h>
#include <blob/errno.h>
#include <blob/error.h>
#include <blob/md5.h>
#include <blob/md5support.h>
#include <blob/serial_usb.h>
#include <blob/partition.h>
#else

#include "usb_serial_download.h"
#include "irq.h"
#include "pxa_usb.h"
#include "time.h"
#include "bvd_usb_ctl.h"
//#include "flash.h"
#include "bulverde.h"

#define USBDEBUGMOD

#ifdef USBDEBUGMOD
#define USBPRINTK(x) x
#else
#define USBPRINTK(x)
#endif //USBDEBUGMOD

#include "config.h"
#include "types.h"
#include "serial_usb.h"
#include "util.h"
#include "tat.h"
#include "bcm2121.h"

#endif

#ifdef CONFIG_MD5_SUPPORT
#define MD5TAG "md5:\r"
#define MD5SIZE 16
#endif

#define STARTAG "start"
#define ENDTAG "end\r"
#define EXITAG "exit"
#define ACKTAG "ACK\r"
#define NAKTAG "NAK\r"
#define COMMANDSIZE 64

#define strsizeof(x) (sizeof(x)-1)

//The following is added by Bill Chen
int iTotalReceiveLength=0;
static int iFirstpartTag=0;
int iIsProgramFs=0;
int icwp=0;
//The following is added to download bootload with USB
static int iIPL=0;

#define FIRSTPARTLENGTH 0x1400000
//#define FIRSTPARTLENGTH 20971520 0x1400000

/* receive a image file to dedicated memory address
    support md5 check now, if you send image as this
    |data of packet1|16 bytes md5 digest|md5:\r|
    |data of packet2|16 bytes md5 digest|md5:\r|
                ...
    |data of packetn|16 bytes md5 digest|md5:\r|
            ....
*/

int UsbSerialReceive(char *addr, int len)
{
    int size,total=0;// for image size
    int lastsize=0;// for progress bar
    int packsize=0;//current packsize
    char command[COMMANDSIZE+1];// keep received command
    int comlen=0;// current command len

    while (1) {
        comlen=0;
        while (comlen < COMMANDSIZE) {
            size = usb_serial_read(command + comlen, COMMANDSIZE - comlen);
            if (size) {
                comlen += size;
                command[comlen] = '\0';
                if (command[comlen - 1] == '\r') {
                    comlen--;
                    command[comlen] = '\0';
                    break;
                }
                else
                    if( verbose_level > 0 )
                        printk("%s", command+comlen - size);
            }
        }
        if( verbose_level > 0 )
            printk("\ncurrent command:(%s)\n", command);

        if (comlen == strsizeof(STARTAG)
                && !strncmp(command, STARTAG, strsizeof(STARTAG))) {
            usb_serial_write(ACKTAG, strsizeof(ACKTAG));
            if ( verbose_level > 0 ) {
                printk("start to receive binary images\n");
                //fix me: hope no end\\r in image file
                printk("please send 'end\\r' as finish tag\n");
            }
            total = lastsize = packsize = 0;
            while (total + packsize < len) {
#ifdef CONFIG_MD5_SUPPORT
                u32 md5_count[MD5SIZE / 4];
#endif
                size = usb_serial_read(addr + total + packsize,
                                len - (total + packsize));
                if (size) {
                    packsize += size;
                    if (packsize - lastsize >= 65535) {
                        lastsize = packsize;
                        if ( verbose_level > 0 )
                            printk(".");
                    }
                    if (*(addr + total + packsize - 1) == '\r'){
#ifdef CONFIG_MD5_SUPPORT
                        if (packsize >= MD5SIZE + strsizeof(MD5TAG)
                                && !strncmp(addr + total + packsize - strsizeof(MD5TAG),
                                MD5TAG, strsizeof(MD5TAG))) {
                            printk("*");
                            md5_buffer(addr + total,
                                            packsize - (MD5SIZE + strsizeof(MD5TAG)),
                                            md5_count);//md5
                            if (!memcmp(addr + total + packsize - (MD5SIZE + strsizeof(MD5TAG)),
                                    md5_count, MD5SIZE)) {//md5 right
                                total += packsize - (MD5SIZE + strsizeof(MD5TAG));
                                usb_serial_write(ACKTAG, strsizeof(ACKTAG));
                            }
                            else {
                                printk("x");
                                usb_serial_write(NAKTAG, strsizeof(NAKTAG));
                            }
                            packsize = lastsize = 0;
                        }
                        else
#endif
                        if (packsize >= strsizeof(ENDTAG)
                                && !strncmp(addr + total + packsize - strsizeof(ENDTAG),
                                ENDTAG, strsizeof(ENDTAG))) {
                            //last packet no MD5? or all data just in one packet and without md5
                            total += packsize - strsizeof(ENDTAG);
                            usb_serial_write(ACKTAG, strsizeof(ACKTAG));
                            break;
                        }
                    }
                }
            }
            if( verbose_level > 0 )
                printk("end tag received, total (%d) bytes got\n",total);
        }
        else if (comlen == strsizeof(EXITAG)
                && !strncmp(command, EXITAG, strsizeof(EXITAG))) {
            usb_serial_write(ACKTAG,strsizeof(ACKTAG));
            if( verbose_level > 0 )
                printk("exit tag received, total (%d) bytes got finially\n",
                                total);
            break;
        }
        else {
            usb_serial_write(NAKTAG, strsizeof(NAKTAG));
            if( verbose_level > 0 )
                printk("Unknown command.\n");
        }
    }
    return total;
}

int udownload_cmd(u32 *destAddress, u32 bufLen)
{
    int numRead;
    static int firstin=1;

    if (verbose_level > 0) {
        printk("Entering serial over usb commandline... \n");
        printk("App protocol:\n"
            "APP                     BLOB\n"
            "start\\r   -------->            \n"
            "               <--------ACK\\r \n"
            "binary image ------->        \n"
            "end\\r  --------->             \n"
            "               <--------ACK\\r \n"
            "exit\\t ---------->            \n"
            "               <--------ACK\\r \n"
            "\n"
            "\n"
            "App first send start\\r to request to start transfer image \n"
            "after receive \"ACK\\r\" from BLOB then app send binary image.\n"
            "App send end\\r to indicate end of image.\n"
            "Also BLOB will send \"ACK\\r\" to App.\n"
            "App send exit\\r to leave udownload. \n");
    }

    if (firstin) {
        usb_serial_init();
        firstin = 0;
    }
    //max 32M
    bufLen=0x01F00000;
#ifdef CONFIG_MD5_SUPPORT
    bufLen+=MD5SIZE+strsizeof(MD5TAG);
#endif
    numRead = UsbSerialReceive((char *)destAddress, bufLen);

    if (verbose_level > 0) {
        printk("Received %i (0x%08x) bytes ", numRead, numRead);
        printk("at 0x%08x\n", (unsigned int)destAddress);
    }
    return 0;
}

/**************************************************************/
char GetCheckSum( char* buffer, int size );
int GetCommandLine( char* buffer );
void DoUsbClientResponse( enum UsbResponseType type, char respara );
static void DoUsbImageDownload( void );
void DoUsbReadDataChunk( char* buffer, int len );
void DoUsbShakehand( void );

char GetCheckSum(char* buffer, int size)
{
    char checksum = 0;
    int i;

    for (i = 0 ; i < size ; i++)
        checksum += buffer[i];

    return checksum;
}

/*
    1 byte: command type
    2--3 byte: command private data
    4 byte: checksum
*/
int GetCommandLine(char* buffer)
{
    DoUsbReadDataChunk( buffer, USBCOMMANDLEN );

    if (buffer[USBCOMMANDLEN-1] != GetCheckSum(buffer, USBCOMMANDLEN-1)) {
        USBPRINTK( printk( "Get checksum error command!\r\n" ) );
        DoUsbClientResponse( UsbResponseAgain, 0 );
        return FALSE;
    }

    return TRUE;
}

/*
    1 byte: UsbClientResponse
    2 byte: type
    3 byte: parameter
    4 byte: checksum
*/
void DoUsbClientResponse(enum UsbResponseType type, char respara)
{
    char UsbCommand[USBRESPONSELEN+1];

    //USBPRINTK( printk( "DoUsbClientResponse begin!\r\n" ) );

    UsbCommand[0] = UsbClientResponse;
    UsbCommand[1] = type;
    UsbCommand[2] = respara;
    UsbCommand[USBRESPONSELEN-1] = GetCheckSum( UsbCommand, USBRESPONSELEN - 1 );
    UsbCommand[4] = 0;

    usb_serial_write( UsbCommand, USBRESPONSELEN );

    //USBPRINTK( printk("DoUsbClientResponse OK!\r\n" ) );
}

/*
        Response exit.
*/
void DoUsbExit(void)
{
    DoUsbClientResponse( UsbResponseExit, 0 );
    USBPRINTK( printk( "Exit command!\r\n" ) );
    msleep( 2000 );
}

/*
    read a chunk of data from USB
*/
void DoUsbReadDataChunk(char* buffer, int len)
{
    int receivelen = 0;

    //USBPRINTK( printk("Chunk len:%d\r\n", len ) );

    if (len) {
        do {
            receivelen += usb_serial_read( buffer + receivelen,
                            len - receivelen );
        } while (receivelen != len);
    }
}

short GetDataLength(char* buffer)
{
    short length;

    length = (short)( buffer[1] << 8 | buffer[2] );
    return length;
}

#define MD5RESLEN 16
void print_md5string(char* buffer)
{
    int i;
    for (i = 0; i < MD5RESLEN; i++)
        USBPRINTK( printk( "%02x", buffer[i] ) );

    USBPRINTK( printk( "\r\n" ) );
}

extern int get_md5_string(char *start_address, int length,
                unsigned char *md5_string);
int CheckDataMd5(char* buffer, int size)
{
    int len;
    char currentmd5result[MD5RESLEN + 2];
    char receivemd5result[MD5RESLEN + 2];

    if (size < MD5RESLEN)
        return - 1;
    len = size - MD5RESLEN;

    memcpy( receivemd5result, buffer+len, MD5RESLEN );
    get_md5_string( buffer, len, currentmd5result );
    //print_md5string( currentmd5result );
    //print_md5string( receivemd5result );

    return memcmp( receivemd5result, currentmd5result, MD5RESLEN );
}

int g_PackageCount = 0;
int DoUsbClientReceive(char* commandbuffer, int offset)
{
    int len = 0;
// char* buffer = (iFirstpartTag)
           ? ((char*)( 0xa0000000 + offset +FIRSTPARTLENGTH))
           : ((char*)( 0xa0000000 + offset ));
    char* buffer = (char*)( 0xa0000000 + offset );
    int md5res;
//

    //Firstly response command!
    DoUsbClientResponse( UsbResponseOK, 0 );
    len = GetDataLength( commandbuffer );
    do {
        DoUsbReadDataChunk( buffer, len );
        md5res = CheckDataMd5( buffer, len );
        if (md5res != 0) {
            DoUsbClientResponse( UsbResponseAgain, 0 );
            USBPRINTK( printk( "Data md5 error!\r\n" ) );
        }
        else {
            //USBPRINTK( printk( "Package:%d\r\n", g_PackageCount ) );
            g_PackageCount++;
            DoUsbClientResponse( UsbResponseOK, 0 );
        }
    } while (md5res != 0);
    return (len-MD5RESLEN);
}

/*
    1 byte: UsbClientFlash
    2 byte: type
    3 byte: reserve
    4 byte: checksum
*/

extern int doc_BDTL_mount (unsigned int partition);
extern int doc_BDTL_dismount (unsigned int partition);
extern int doc_oneblock_BDTL_write (unsigned int partition, unsigned char *buf, unsigned int block);

void DoUsbClientFlash(int len, char type)
{
    unsigned int flash_address, ram_address;
    unsigned int image_length;
    int i, blocknum;
    unsigned int partitionNo;
    unsigned int bdtl_flag = 0;

    switch ( type ) {
        case FlashImageTypeTAT:
        {
            bdtl_flag=0;

            if(iIPL==0) { //First to download the IPL
                if(!WriteIPL(0))
                    printk("Write IPL Failed!\n");
                else
                    printk("Write IPL Successful!\n");

                iIPL=1;
                DoUsbClientResponse ( UsbResponseFlash, 1 );
                DoUsbClientResponse ( UsbResponseFlash, 1 );
            }
             else {  //Second to download the SPL
                if(!WriteSPL(0))
                    printk("Write SPL Failed!\n");
                else
                    printk("Write SPL Successful!\n");

                iIPL=0;
                DoUsbClientResponse ( UsbResponseFlash, 1 );
                DoUsbClientResponse ( UsbResponseFlash, 1 );
            }
        }//the end of the local function to deal with the download updatation. //20060215
        break;

        case FlashImageTypeKernel:
        bdtl_flag = 0;
        printk("DoUsbClientFlash------FlashImageTypeKernel\n");
        WriteKernel();
        DoUsbClientResponse ( UsbResponseFlash, 1 );
        DoUsbClientResponse ( UsbResponseFlash, 1 );
        break;

        case FlashImageTypeRootFS:
        bdtl_flag = 1;
        /* WriteRootFs(); */
                                partitionNo = 0;
        image_length = BDTL0_LENGTH;
        doc_BDTL_mount(partitionNo);
        break;

        case FlashImageTypeUserFS:
        bdtl_flag = 1;
        partitionNo = 1;
        image_length = BDTL1_LENGTH;
            doc_BDTL_mount (partitionNo);
        break;
 /*
        case FlashImageTypeUserDataFS:
        bdtl_flag = 1;
        partitionNo = 1;
            doc_BDTL_mount (partitionNo);
        break;
    */
#ifdef DOC_SIZE_128M
    case FlashImageTypeUserDataFS:
        bdtl_flag = 1;
        partitionNo = 2;
        image_length = BDTL2_LENGTH;
        doc_BDTL_mount (partitionNo);
        break;
#endif

        case FlashImageTypePTable:
        bdtl_flag = 1;
        partitionNo = 1;
            doc_BDTL_mount (partitionNo);
        break;

    case FlashImageTypeInitImage:
        bdtl_flag = 0;
        flash_address = INITIMAGE_FLASH_START;
        image_length = INITIMAGE_FLASH_LENGTH;
        break;

    case FlashImageTypeGSMModule:
        DoUsbClientResponse( UsbResponseFlash, 1);
        if (!bcm2121_imagedownload(0x10000 ,len, 115)) {
            printk("bcm2121 download succeeded\n");
            DoUsbClientResponse( UsbResponseFlash , 1);
        }
        else {
            printk("bcm2121 download failed \n");
            DoUsbClientResponse( UsbResponseError, 0);
            return;
        }
        break;

    case FlashImageTypeDspPatch:
        DoUsbClientResponse( UsbResponseFlash, 1);
        if ( !bcm2121_imagedownload(0x003c0000, len, 115)) {
            printk("GSM Dsp patch download succeeded \n");
            DoUsbClientResponse( UsbResponseFlash, 1);
        }
        else {
            printk("GSM Dsp patch downlaod failed \n");
            DoUsbClientResponse( UsbResponseError, 0);
        }
        break;

    case FlashImageTypeSysParmInd:
        DoUsbClientResponse( UsbResponseFlash, 1);
        if ( !bcm2121_imagedownload(0x003d0000, len, 115)) {
            printk("GSM Sys Parameter Ind download succeeded \n");
            DoUsbClientResponse( UsbResponseFlash, 1);
        }
        else {
            printk("GSM Sys Parameter Ind downlaod failed \n");
            DoUsbClientResponse( UsbResponseError, 0);
        }
        break;

    case FlashImageTypeSysParmDep:
        DoUsbClientResponse( UsbResponseFlash, 1);
        if ( !bcm2121_imagedownload(0x003e0000, len, 115)) {
            printk("GSM Sys Parameter Dep download succeeded \n");
            DoUsbClientResponse( UsbResponseFlash, 1);
        }
        else {
            printk("GSM Sys Parameter Dep downlaod failed \n");
            DoUsbClientResponse( UsbResponseError, 0);
        }
        break;

        default:
        DoUsbClientResponse( UsbResponseError, 0 );
        USBPRINTK( printk( "Flash error command!\r\n" ) );
        return;
    }

    if(bdtl_flag == 1) {
        if((len % (128 * 512)) == 0)
            blocknum = len / ( 128 * 512 );
        else
            blocknum = len / ( 128 * 512 ) + 1;
        ram_address = RAM_START;
        DoUsbClientResponse( UsbResponseFlash, 1);

        switch(type) {

            case FlashImageTypeRootFS:
            case FlashImageTypeUserFS:
#ifdef DOC_SIZE_128M
            case FlashImageTypeUserDataFS:
#endif
                for ( i = 1; i <= blocknum; i++ ) {
                    doc_oneblock_BDTL_write(partitionNo,
                                    ram_address + (i - 1) * 512 * 128, (i - 1));
                      printk("Flash: ------i=%d\n", i);
                }
                break;

            case FlashImageTypePTable:
                for ( i = 1; i <= blocknum; i++ ) {
                    doc_oneblock_BDTL_write(partitionNo,
                                    ram_address + (i - 1)  * 512 * 128,
                                    (i - 1) + 654);
                    printk("Flash: ------i = %d\n", i);
                }
                break;

            default:
                break;

        }//end of the latest switch(type)

error:
        DoUsbClientResponse( UsbResponseFlash, 1);
        printk("Bill:----The following is dismount DOC TAB\n");
        doc_BDTL_dismount (partitionNo);
        blocknum = 0;
        bdtl_flag = 0;

    } //End of if(bdtl_flag == 1)

    iTotalReceiveLength = 0;
    icwp = 0;
    USBPRINTK( printk( "Write Doc End!\r\n" ) );
}

extern int usb_connected;
extern unsigned char usb_connecting_check;
static int my_check_usb_connection(void)
{
       //unsigned int cnt = 0;

#if 0
    /* waiting util connected */
    USBPRINTK( printk("my_check_usb_connection begin!\n") );

    while (cnt < 0x100000 && !usb_connected) {
        if (ICPR & 0x800)
            udc_int_hndlr( 0x11, 0 );
        cnt++;
    }

    USBPRINTK( printk("my_check_usb_connection end--cnt:0x%x\n", cnt ) );
#else
    /* Check if the usb cable is connect to PC */
/* #define GPDR1_B_USB_DEC_N (0x01 << 3) */
/*         if( !(GPLR1 & GPDR1_B_USB_DEC_N) ) { */
/*                 /\* Cable is connected *\/ */
/*                 return 1; */
/*         } */

    if (usb_connecting_check)
        return 1;
#endif
    return 0;
}

static void usb_serial_exit(void)
{
    pxa_usb_stop();
    pxa_usb_close();
}

static int UsbConnectStatus(void)
{
    int ret;

    //init USB port.
    USBPRINTK( printk( "USB serial init!\r\n" ) );
    if (usb_serial_init()) {
        printk("Cable isn't attached !\n");
        return FALSE;
    }
    else {
        printk("Cable is connected !\n");
    }
    USBPRINTK( printk( "USB serial OK!\r\n" ) );

    ret = my_check_usb_connection();
    USBPRINTK( printk( "usb_connected:%d\r\n", usb_connected ) );

//The following is inverted by Bill Chen 20060301
#if 0
#if 0
    if (usb_connected == 0) {
        usb_serial_exit();
        return FALSE;
    }
#else
    if (usb_connected == 0
            && ret == 0) {
        printk("USB serial exit\n");
        usb_serial_exit();
        return FALSE;
    }
#endif
#endif

    return TRUE;
}

int UsbDownloadImage(void)
{
    int i;
    char UsbCommand[USBCOMMANDLEN];

    if (UsbConnectStatus() == FALSE)
        return FALSE;

    for (i = 0; i < 3; i++) {
        if (1 == usb_serial_read(&(UsbCommand[0]), 1)) {
            //USBPRINTK( printk( "Shakehand data:%c\r\n", UsbCommand[0] ) );
            if (UsbCommand[0] == UsbShakehand) {
                DoUsbReadDataChunk( &(UsbCommand[1]), USBCOMMANDLEN - 1 );
                if (UsbCommand[USBCOMMANDLEN-1]
                        == GetCheckSum(UsbCommand, USBCOMMANDLEN - 1)) {
                    DoUsbShakehand();

                    DoUsbImageDownload();
                    return TRUE;
                }
            }

            USBPRINTK( printk( "Shakehand command error!\r\n" ) );
        }
        else
            USBPRINTK( printk( "Shakehand timeout:%d\r\n", i ) );

        msleep( 500 );//sleep 1 second.
    }

    usb_serial_exit();
    USBPRINTK( printk( "USB can not shakehand with PC!\r\n" ) );

    //while( 1 );
    return FALSE;
}

/*
    USB shakehand command
*/
void DoUsbShakehand(void)
{
    DoUsbClientResponse( UsbResponseShakehand, 0 );
    USBPRINTK( printk( "Shakehand command!\r\n" ) );
}

/*
    USB undefined command
*/
void DoUsbUndefineCommand(void)
{
    DoUsbClientResponse( UsbResponseError, 0 );
    USBPRINTK( printk( "Undefined command!\r\n" ) );
}
extern int g_bWrite;
static void DoUsbImageDownload(void)
{
    char UsbCommand[USBCOMMANDLEN];
    int offset = 0x20000;
    int datalen = 0;
    int olddatalen = 0;
// int icwp=0;
//
//To initialize the iTotalReceiveLength
    iTotalReceiveLength=0;
//

    USBPRINTK( printk( "USB Shakehand PC OK!\r\n" ) );
    g_PackageCount = 0;

    //
    while (1) {
        if (FALSE == GetCommandLine(UsbCommand))
            continue;

        USBPRINTK( printk( "Command:%c\r\n", UsbCommand[0] ) );

        switch (UsbCommand[0]) {
            case UsbShakehand:
                USBPRINTK(printk("UsbShakehand\n"));
                DoUsbShakehand();
                offset = 0x20000;
                datalen = 0;

                g_PackageCount = 0;
                break;

            case UsbClientReceive:
                datalen = DoUsbClientReceive( UsbCommand, offset );
            //USBPRINTK( printk( "Rec:%x\r\n", datalen ) );
                offset += datalen;
                //To caculate the total length and then to passed to DoUsbClientFlash
                iTotalReceiveLength += datalen;
//
            //printk("Bill: ----%c, block = %d\n", UsbCommand[0], ++icwp);
                break;

            case UsbClientSend:
                break;
            case UsbClientResponse:
                break;
            case UsbClientError:
                break;

            case UsbClientFlash:
                printk("Bill:-----total=%d\n", iTotalReceiveLength);
                //   DoUsbClientFlash( datalen, UsbCommand[1] );
                if (g_bWrite) {
                    DoUsbClientFlash( iTotalReceiveLength, UsbCommand[1] );
                }
                else {
                    DoUsbClientResponse ( UsbResponseFlash, 1 );
                    DoUsbClientResponse ( UsbResponseFlash, 1 );
                }
            /* DoUsbExit(); */
            /* usb_serial_exit(); */
            /* return; */
            /* offset = 0; */
            /* datalen = 0; */

                /* g_PackageCount = 0; */

                if (UsbCommand[1] == FlashImageTypeTAT)
                    break;

            case UsbExit:
                DoUsbExit();
                usb_serial_exit();
                return;
            default:
                DoUsbUndefineCommand();
                break;
        }
    }
}

/**************************************************************/

#if 0
char udownload_help[] = "udownload {blob|param|kernel|ramdisk}\n"
"Download <argument> image to RAM using serial over usb\n";
#endif
